﻿/*
 *  Original Code:
 *  
 *  The OpenGL Obj Loader
 *
 *  Created by Seb on 07/12/05.
 *  Copyright 2005 The Old Game Factory. All rights reserved.
 *
 */

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Text;
using Auxiliary.VecMath;

namespace Simple3DRedactor.Domain
{
    public class PolygonFace
    {
        private readonly List<int> _v = new List<int>();	//	vertex indices
        private readonly List<int> _vt = new List<int>();	//	texture indices
        private readonly List<int> _vn = new List<int>();	//	normal indices

        public void AddVertTexNorm(int vi, int ti, int ni)
        {
            _v.Add(vi);
            _vt.Add(ti);
            _vn.Add(ni);
        }

        public ReadOnlyCollection<int> VertexIndexies { get { return new ReadOnlyCollection<int>(_v); } }
        public ReadOnlyCollection<int> TextureIndexies { get { return new ReadOnlyCollection<int>(_vt); } }
        public ReadOnlyCollection<int> NormalIndexies { get { return new ReadOnlyCollection<int>(_vn); } }

        internal void FillNormalIndexies(int val)
        {
            for (int i = 0; i < _vn.Count; ++i)
                _vn[i] = val;
        }
    }

    public class Mesh
    {
        private const int FirstIndex = 1;

        private readonly List<Vector3D> _vertices = new List<Vector3D>();
        private readonly List<Vector2D> _texCoords = new List<Vector2D>();
        private readonly List<Vector3D> _normals = new List<Vector3D>();

        private readonly List<PolygonFace> _faces = new List<PolygonFace>();

        public Vector3D MinPoint { get; private set; }
        public Vector3D MaxPoint { get; private set; }

        public ReadOnlyCollection<Vector3D> Vertices { get { return new ReadOnlyCollection<Vector3D>(_vertices); } }
        public ReadOnlyCollection<Vector2D> TexCoords { get { return new ReadOnlyCollection<Vector2D>(_texCoords); } }
        public ReadOnlyCollection<Vector3D> Normals { get { return new ReadOnlyCollection<Vector3D>(_normals); } }

        public ReadOnlyCollection<PolygonFace> Faces { get { return new ReadOnlyCollection<PolygonFace>(_faces); } }

        public string FileName { get; private set; }

        private Mesh()
        {
            MinPoint = new Vector3D(float.MaxValue, float.MaxValue, float.MaxValue);
            MaxPoint = new Vector3D(-float.MaxValue, -float.MaxValue, -float.MaxValue);

            //	Pushing some dummy front values.
            //	Note: this is uggly, find a better way.
            _vertices.Add(Vector3D.Zero);
            _texCoords.Add(Vector2D.Zero);
            _normals.Add(Vector3D.Zero);
        }

/*
        private void Clear()
        {
            MinVector = new Vector3D(float.MaxValue, float.MaxValue, float.MaxValue);
            MaxVector = new Vector3D(-float.MaxValue, -float.MaxValue, -float.MaxValue);

            _vertices.Clear();
            _texCoords.Clear();
            _normals.Clear();
            _faces.Clear();

            //	Cf. ctor.
            _vertices.Add(Vector3D.Zero);
            _texCoords.Add(Vector3D.Zero);
            _normals.Add(Vector3D.Zero);
        }
*/

        private void AddVertex(Vector3D v)
        {
	        _vertices.Add(v);

            MinPoint = Vector3D.Min(MinPoint, v);
            MaxPoint = Vector3D.Max(MaxPoint, v);
        }

        private void AddTextureCoord(Vector2D vt)
	    {
	        _texCoords.Add(vt);
	    }

        private void AddNormal(Vector3D vn)
	    {
	        _normals.Add(vn);
	    }

        private void AddFace(PolygonFace face)
        {
            _faces.Add(face);
        }

        private void GenerateFlatNormals()
	    {
	        if (HasNormals)
		        return;
	
	        // for each face...
	        foreach (var face in _faces)
	        {
		        var v1 = _vertices[face.VertexIndexies[0]];
                var v2 = _vertices[face.VertexIndexies[1]];
                var v3 = _vertices[face.VertexIndexies[2]];

		        var v = new Vector3D(v2.X - v1.X, v2.Y - v1.Y, v2.Z - v1.Z);
                var w = new Vector3D(v3.X - v1.X, v3.Y - v1.Y, v3.Z - v1.Z);
                var vn = new Vector3D(v.Y * w.Z - v.Z * w.Y, v.Z * w.X - v.X * w.Z, v.X * w.Y - v.Y * w.X);

		        var length = Vector3D.Length(vn);

		        if (0 < length) 
                {
			        var a = 1.0f / length;
                    vn *= a;
		        }

                _normals.Add(vn);

		        //	register the generated normal through the face
                face.FillNormalIndexies(_normals.Count - 1);
	        }
	    }
        
	    public bool HasNormals { get { return _normals.Count > FirstIndex; } }

        public bool HasTextureCoords { get { return _texCoords.Count > FirstIndex; } }

        public int NumVertices { get { return _vertices.Count - FirstIndex; }}

	    public int NumTextureCoords { get { return _texCoords.Count - FirstIndex; } }

	    public int NumNormals { get { return _normals.Count - FirstIndex; } }

        public int NumFaces { get { return _faces.Count; } }

        public static Mesh LoadFromObjFile(string fileName)
        {
            var mesh = new Mesh();

            var file = new FileInfo(fileName);

            if (file.Exists)
            {
                using (var stream = file.OpenText())
                {
	                while (SkipCommentLine(stream)) 
	                {
                        if (!ProcessLine(mesh, stream))
			                break;
	                }
                }

                mesh.GenerateFlatNormals();
                mesh.FileName = fileName;
            }

            return mesh;
        }

        private static bool SkipCommentLine(StreamReader stream)
        {
	        while (stream.Peek() >= 0) 
            {
		        if ('#' == stream.Peek())
                    SkipLine(stream);
		        else
			        return true;
            }

            return false;
        }

        private static void SkipLine(StreamReader stream)
        {
            stream.ReadLine();
        }

        private static string ReadString(StreamReader stream)
        {
            var builder = new StringBuilder();

            while (stream.Peek() >= 0 && !char.IsWhiteSpace((char) stream.Peek()))
            {
                builder.Append((char) stream.Read());
            }

            while (stream.Peek() >= 0 && char.IsWhiteSpace((char)stream.Peek()))
            {
                stream.Read();
            }

            return builder.ToString();
        }

        private static int ReadInt(StreamReader stream)
        {
            var builder = new StringBuilder();

            while (stream.Peek() >= 0 && char.IsDigit((char)stream.Peek()))
            {
                builder.Append((char)stream.Read());
            }

            return int.Parse(builder.ToString());
        }

        private static bool ProcessLine(Mesh mesh, StreamReader stream)
        {
            if (stream.Peek() < 0)
                return false;

            var eleId = ReadString(stream);
	
	        if (string.IsNullOrEmpty(eleId))
		        return true;

	        float x, y, z;  
	
	        switch (eleId)
	        {
	            case "v":
                    x = float.Parse(ReadString(stream), CultureInfo.InvariantCulture);
                    y = float.Parse(ReadString(stream), CultureInfo.InvariantCulture);
                    z = float.Parse(ReadString(stream), CultureInfo.InvariantCulture);
                    mesh.AddVertex(new Vector3D(x, y, z));
	                break;
	            case "vt":
                    x = float.Parse(ReadString(stream), CultureInfo.InvariantCulture);
                    y = float.Parse(ReadString(stream), CultureInfo.InvariantCulture);
                    //try
                    //{
                    //    z = float.Parse(ReadString(stream), CultureInfo.InvariantCulture);
                    //}
                    //catch
                    //{
                    //    // is z (i.e. w) is not available
                    //    z = 0.0f;
                    //}
                    mesh.AddTextureCoord(new Vector2D(x, y));
	                break;
	            case "vn":
                    x = float.Parse(ReadString(stream), CultureInfo.InvariantCulture);
                    y = float.Parse(ReadString(stream), CultureInfo.InvariantCulture);
                    z = float.Parse(ReadString(stream), CultureInfo.InvariantCulture);
                    mesh.AddNormal(new Vector3D(x, y, z));
	                break;
	            case "f":
	                {	
                        //	face data
	                    //	face treatment
	                    //  Note: obviously this technique only works for convex polygons with ten verts or less.
	                    var vi = new int[10];                                // vertex indices.
	                    var ni = new[] { -1, -1, -1, -1, 0, 0, 0, 0, 0, 0 }; // normal indices.
	                    var ti = new[] { -1, -1, -1, -1, 0, 0, 0, 0, 0, 0 }; // tex indices.

	                    int i = 0;
	                    for (; i < 10; ++i)
	                    {
	                        try
	                        {
	                            if (!mesh.HasTextureCoords && !mesh.HasNormals)
	                            {
                                    vi[i] = ReadInt(stream);
	                            }
	                            else if(!mesh.HasTextureCoords)
	                            {
                                    vi[i] = ReadInt(stream);
                                    stream.Read();
                                    stream.Read();
                                    ni[i] = ReadInt(stream);
	                            }
	                            else if(!mesh.HasNormals)
	                            {
                                    vi[i] = ReadInt(stream);
                                    stream.Read();
                                    ti[i] = ReadInt(stream);
	                            }
	                            else
	                            {
                                    vi[i] = ReadInt(stream);
                                    stream.Read();
                                    ti[i] = ReadInt(stream);
                                    stream.Read();
                                    ni[i] = ReadInt(stream);
                                }
                                stream.Read();
	                        }
	                        catch
	                        {
	                            break;
	                        }
	                    }

	                    //	Create the polygon face
	                    var face = new PolygonFace();

	                    for (int k=0; k < i; ++k)
	                        face.AddVertTexNorm(vi[k], ti[k], ni[k]);

	                    mesh.AddFace(face);
	                }
	                break;
	            default:
	                SkipLine(stream);
	                break;
	        }
	        return true;
        }
    }
}
